Auditoria Avançada com Hibernate Envers
Por Carlos Silva,
Postado em Novembro 2016
Adicione informações personalizadas a sua auditoria.
Por que este artigo é útil: Neste artigo veremos como monitorar as ações realizadas sobre as entidades auditáveis de um sistema, tendo como objetivo prover total controle sobre o uso do software e resguardar a integridade das informações. Aprenderemos através do desenvolvimento de uma aplicação web como auditar informações que vão além daquelas fornecidas por padrão pelo framework, adicionando customizações ao armazenamento das revisões, como identificar o usuário que realizou a alteração e mesmo o IP da sua máquina, além de aprender a recuperar e manipular as informações geradas através do processo de auditoria.
Desde pequenas a aplicações de grande porte, a necessidade de monitorar as ações realizadas por um usuário frente as diversas funcionalidades existentes são reais. Em um sistema de uma instituição financeira, por exemplo, existe inclusive uma motivação legal para que seja registrada todas as alterações realizadas nos saldos das contas para uma eventual análise posterior.
Prover o controle sobre o uso da aplicação, bem como resguardar a integridade das informações administradas pela mesma são de extrema valia e geralmente é uma exigência e um requisito oriundo da área de Segurança da Informação da corporação que é responsável por especificar, por exemplo, quais ações serão rastreadas, quais dados serão armazenados, por quanto tempo, como serão consultados, quem poderá consultá-los etc. Essa demanda, por sua vez, é repassada à área de Tecnologia da Informação (TI) que geralmente é a detentora das informações e a responsável por proteger e garantir a consistência dos dados que na maioria das vezes, ficam internalizados em banco de dados relacionais. Dessa forma, cabe a equipe de TI decidir qual o mecanismo técnico e procedimento capaz de gerar um registro de histórico das ações executadas.
Existem algumas técnicas clássicas que são empregadas, como por exemplo, utilizar-se de recursos de banco, entre eles Stored Procedure e Triggers. O problema desse tipo de abordagem é o alto acoplamento existente entre os recursos criados e o banco de dados.
No cenário atual a grande maioria dos projetos Java lançam mão de frameworks ORM (mapeamento objeto relacional) para a camada de persistência. Certamente um dos mais utilizados pela comunidade Java é o Hibernate que permite a diminuição da complexidade existente na construção de aplicações que trabalham com banco de dados relacional. Em cima desse cenário, e diante da necessidade de rastrear as ações realizadas sobre as entidades de uma aplicação, surgiu o subprojeto Envers.
O Hibernate Envers oferece a organização do histórico das versões dos dados trabalhados pela aplicação, através das entidades mapeadas para a persistência JPA para auditar as modificações ocorridas em um dado registro. Dessa forma, com sua utilização, uma aplicação é capaz de gerir todas as modificações realizadas no seu database de forma fácil e não intrusiva.
Um processo de auditoria bem modelado deve fornecer algumas informações básicas sobre as operações realizadas sobre um sistema, como: quem consultou informações, quais informações foram essas, quem excluiu, o que foi excluído, o que foi editado, como estavam antes e como ficaram depois; quando foi feito, etc. De posse dessa fotografia e caso algum dado seja inserido ou alterado de forma equivocada, o analista terá modos para recuperar a versão antiga dos dados e inferir quem foi o autor daquela ação.
Neste artigo será apresentada algumas das principais características do Hibernate Envers, detalhando-os e mostrando na prática o controle feito em uma aplicação simples, completa e integrada com JSF, e que tem como um dos seus requisitos a necessidade de rastrear as operações efetuadas pelos seus usuários. Como tecnologias serão utilizadas, além dos frameworks já mencionados, a linguagem Java, o Eclipse, o Apache Maven, o MySQL, o Tomcat e o Hibernate que viabilizará o contato entre a aplicação e o banco de dados.
Conhecento o Hibernate Envers
O Hibernate Envers é uma biblioteca que permite criar facilmente, auditória de classes persistentes através do controle de versões de persistência em mapeamentos objetos relacionais feitos através do Hibernate.
Para cada entidade mapeada que seja auditada, uma tabela versionada é criada no banco de dados, contendo todo o histórico das alterações efetuadas sobre aquela entidade. Basicamente, cada transação realizada no banco é classificada como uma revisão (ao menos que essa transação não realize nenhuma modificação), sendo que cada nova revisão gera alimentação automática das tabelas que permitem o versionamento das classes persistentes. Em suma, cada vez que uma tabela sinalizada para auditoria sofre alterações em seus dados (registros), uma nova "versão" dela é gerada pelo Envers e armazenada em uma outra tabela, que contém como chave primária o atributo de revisão, além do tipo de operação e de todos os campos auditáveis da tabela alvo. Podemos comparar o Envers a sistemas de controle de versão, como Subversion, CVS, Git que mantêm revisões globais para todas as mudanças ocorridas.
Dessa forma, o analista pode recuperar e consultar dados históricos sem muito esforço, sendo possível, por exemplo, verificar quais informações foram alteradas naquela revisão, o dia em que aquela alteração ocorreu, em qual momento, quem realizou tal mudança e até mesmo registrar outras informações que julgar imprescindíveis. Em cima disso, é possível identificar comportamentos indevidos da aplicação por parte dos seus usuários e até mesmo recuperar dados que não deveriam ter sofridos alterações.
Como vantagens da utilização desse framework podemos citar: permite auditar todas as entidades mapeadas pelo Hibernate; baseado em revisões; independência do fabricante para o banco de dados; agrega valor ao produto; redução no custo de manutenção e maior produtividade.
Outra vantagem do Envers (Easy Entity Versioning) é a sua fácil implementação uma vez que sua utilização se resume a incluir no código da classe mapeada a anotação @Audited e inserir algumas poucas classes de listener na configuração do projeto. Feito isso, a aplicação está pronta para registrar as alterações ocorridas através da tabela de históricos referente a entidade que será criada automaticamente no banco de dados. Caso algum atributo da entidade não necessite ser auditado, podemos lançar mão de outra anotação que é a @NotAudited.
Para que o Envers trabalhe de forma adequada, todas as entidades que passarão por processo de auditoria devem ter chaves primárias (identificadores exclusivos) imutáveis.
Configuração do Ambiente de Desenvolvimento Java Após abordarmos o contexto do artigo e mencionarmos os principais objetivos deste trabalho, vamos seguir para a parte prática, com o desenvolvimento de uma aplicação web que armazenará além das informações default do framework, outras, como o nome do usuário que fez a alteração e o IP de sua máquina.
O primeiro passo nesse processo é ter o ambiente de desenvolvimento corretamente configurado, sendo que a primeira escolha a ser feita é a do Ambiente de Desenvolvimento Integrado (IDE) a ser utilizado. Optamos pelo Eclipse Luna Java EE, por ser open-source, propiciar maior produtividade durante a construção e por ser amplamente utilizado pela comunidade Java. Trabalhando com o Eclipse, de forma integrada, lançaremos mão do Apache Maven 3.1.1, que oferece maior simplicidade no gerenciamento do projeto e das bibliotecas.
O endereço para download das ferramentas citadas encontra-se na seção Links. Nos dois casos será descarregado na máquina do desenvolvedor um arquivo compactado. Para instalação de ambos, descompacte-os em uma pasta de preferência dentro do sistema de arquivos. É necessário ter um JDK instalado no sistema. Escolhemos a versão 8 do Java.
Para a persistência dos dados optamos por utilizar o SGBD (Sistema Gerenciador de Banco de dados) MySQL 6.3. Portanto, caso não tenha este software instalado em sua máquina, realize seu download (veja seção Links) e proceda com sua instalação. Além disso, é necessário ter acesso, de forma local ou remota, a um servidor web que implemente as bibliotecas do Java EE para execução da aplicação. Escolhemos o Tomcat local sendo necessário realizar sua adição ao Eclipse. Isso é feito, facilmente, através da aba Servers do IDE clicando em Add Servers e selecionando o diretório de instalação do Tomcat, que nesse caso foi utilizado em sua versão 8.0.33.
Desenvolvendo o cadastro de alunos
Com todas as ferramentas instaladas e o banco de dados inicializado, para apresentar a tecnologia proposta, podemos partir para o desenvolvimento da aplicação web MVC com JavaServer Faces (JSF). O objetivo deste sistema será gerenciar o cadastro de alunos de uma instituição educacional e para isso será construído um formulário simples para internalizar as informações na base de dados. O código-fonte completo desta aplicação pode ser obtido através da página da revista.
Como o intuito maior desse artigo é abordar o Hibernate Envers, de forma a simplificarmos e concentrarmos tão somente no entendimento desta tecnologia, nosso sistema conterá apenas duas entidades de domínio, chamadas Aluno e Usuário que não possuem relacionamento, e a partir daí serão construídas as ações do CRUD (Create-Read-Update-Delete).
As propriedades de um aluno serão: código, nome, matricula, CPF, email e cidade. Já um usuário possuirá nome e senha.
A Figura 1 mostra a representação gráfica da entidade de domínio do sistema.
Figura 1. Representação gráfica da tabela Aluno
Criando a aplicação no Eclipse com o Maven
Para iniciar a construção, crie um novo projeto Java, a partir do Maven no IDE, através do menu File > New > Project. Na tela que surgir, selecione Maven Project e clique em Next. Em seguida, na janela New Project Maven, marque a opção Create a simple project (skip archetype selection) e clique em Next.
Nesse ponto, iremos realizar a identificação do projeto, como Figura 2. Os campos Group ID, Artifact ID (nome do projeto), Packaging e Version devem ser preenchidos, respectivamente, com os seguintes valores: br.com.devmedia, hibernate-envers-web, war e 0.0.1-SNAPSHOT. Após o preenchimento, clique em Finish.
Feito isso, teremos o projeto criado. Porém, ainda precisamos atualizar o projeto para que tenhamos sua estrutura pronta e as configurações do Maven definidas. Portanto, siga até o menu Maven > Update Project, selecione o projeto recém-criado e clique em OK.
Figura 2. Novo Projeto Maven.
Adicionando as dependências necessárias Com a estrutura do projeto Maven pronta, o próximo passo é inserir as bibliotecas que serão empregadas no projeto. Isso é feito diretamente configurando o arquivo pom.xml, local onde está contida a configuração principal do Maven, através da adição de dependências. As seguintes serão adicionadas:
- Hibernate Core (4.3.11);
- Hibernate Envers (4.3.11);
- Driver do MySQL (5.1.34);
- JSF API (2.2.13);
- JSF IMPL (2.2.13);
- Java EE API (7.0);
- Javax Servlet API (3.1.0).
Portanto, na aba Dependencies do pom.xml, clique em Dependency > Add. Entre com as informações das bibliotecas e em seguida, clique em Ok.
Feito isso, é necessário atualizar o projeto para que as dependências sejam baixadas e incorporadas ao Build Path do projeto. Dessa forma, clique em Maven > Update Project.
A Listagem 1 mostra trecho do arquivo pom.xml contendo todas as dependências adicionadas.
Listagem 1. Configuração do arquivo pom.xml.
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.34</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>4.3.11.Final</version>
</dependency>
<dependency>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-api</artifactId>
<version>2.2.13</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-envers</artifactId>
<version>4.3.11.Final</version>
</dependency>
<dependency>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-impl</artifactId>
<version>2.2.13</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>7.0</version>
</dependency>
</dependencies>
Criando, mapeando e auditando Através de anotações no código, a partir de agora, vamos criar e definir o mapeamento objeto/relacional de nossa entidade, além de sinalizar ao Envers qual entidade sofrerá auditoria.
Na organização do projeto, iremos colocar cada tipo de classe em um pacote diferente. Sendo assim, nossa entidade Aluno ficará no pacote br.com.jm.entidade, devendo o mesmo ser criado.
Para isso, clique sobre o projeto hibernate-envers-web com o botão direito do mouse, selecione New > Package e informe seu nome. Após criar o pacote, vamos criar a classe, clicando com o botão direito do mouse sobre o pacote, escolhendo a opção New > Class e, por fim, nomeando a classe na tela que aparecer. O código da classe Aluno é apresentado na Listagem 2.
Listagem 2. Código da classe Aluno.
package br.com.jm.entidade;
//imports omitidos
@Audited
@Table
@AuditTable(value="aluno_auditoria")
@Entity
public class Aluno implements Serializable {
private static final long serialVersionUID = 1L;
public Aluno(){}
@Id
@Column
@GeneratedValue(strategy = GenerationType.AUTO)
private int codigo;
@Column
public String nome;
@Column
public int matricula;
@Column
public String cpf;
@Column
public String cidade;
@Column
public String email;
// construtor e métodos gets e sets omitidos
}
A anotação principal referente ao Hibernate Envers e que indica que a entidade e suas propriedades passarão por auditoria é a @Audited e pode ser vista na linha 5. Outra anotação da API do Envers aparece na linha 7, @AuditTable, que permite customizar o nome da tabela que será criada para armazenar todas as alterações sofridas pela entidade auditada. Quando não se utiliza essa marcação, o framework utiliza a nomenclatura padrão que consiste do nome da entidade auditada com o sufixo _AUD. As demais anotações são referentes ao Hibernate Core.
Outra classe do domínio da aplicação é a Usuario que será criada dentro desse mesmo pacote. Por questão de simplicidade, essa classe não será persistida, nem mesmo enfrentará o processo de auditoria. O motivo da sua criação é para que possamos identificar o usuário que estará logado na aplicação e assim poder associar o identificador do usuário as alterações feitas na aplicação. O código da classe Usuário pode ser visualizado na Listagem 3.
Listagem 3. Código da classe Usuário.
package br.com.jm.entidade;
public class Usuario {
private String user;
private String pwd;
// construtor e métodos gets e sets omitidos
}
Criando o Controller da aplicação A camada de controle é composta por elementos (Managed Beans ouBeansGerenciados) cuja função é controlar o fluxo de processamento e estabelecer a ligação entre a camada de visão e o modelo.
Os Managed Beans serão criados dentro do pacote br.com.jm.mb. Uma das classes que criaremos dentro desse pacote é a UsuarioBean que tem como função controlar as ações do login e logoff da aplicação.
Para criá-la, clique sobre o pacote, escolha New > Class e use o nome UsuarioBean. Seu código pode ser visto na Listagem 4.
Listagem 4. Código da classe UsuarioBean.
package br.com.jm.mb;
//imports omitidos
@ManagedBean
@SessionScoped
public class UsuarioMB implements Serializable {
private static final long serialVersionUID = 1L;
private Usuario usuario = new Usuario();
public String validateUsernamePassword() {
String login = "matheus";
String senha = "1234";
if (login.equals(usuario.getUser()) && senha.equals(usuario.getPwd())) {
HttpSession session = SessionBean.getSession();
session.setAttribute("username", usuario.getUser());
return "menu";
} else {
return "login";
}
}
public String logout() {
HttpSession session = SessionBean.getSession();
session.invalidate();
return "login";
}
//métodos gets e se
O método validateUsernamePassword, linha 13, verifica se os dados de acesso a aplicação foram inseridos corretamente e, caso isso se confirme, cria uma sessão, linha 18, através do método estático getSession, da classe SessionBean, definindo uma variável como sendo o nome do usuário logado. Por questão de simplificação, foram definidos dentro da classe, nas linhas 14 e 15, o usuário e a senha da aplicação. Caso os dados de acesso sejam inseridos adequadamente o usuário será direcionado para o objeto view correto.
A Listagem 5 mostra a implementação da classe utilitária SessionBean, utilizada pelo bean.
Listagem 5. Código da classe SessionBean.
package br.com.jm.util;
//imports omitidos
public class SessionBean {
public static HttpSession getSession() {
return (HttpSession) FacesContext.getCurrentInstance()
.getExternalContext().getSession(false);
}
public static String getUserName() {
HttpSession session = (HttpSession) FacesContext.getCurrentInstance()
.getExternalContext().getSession(false);
return session.getAttribute("username").toString();
}
}
Outro controlador que criaremos chama-se AlunoBean que tem como função controlar o módulo do sistema que cadastra os alunos, direcionando o fluxo para as várias operações disponíveis. Clique sobre o pacote br.com.jm.mb, escolha New > Class e nomeie a classe. O código desta classe é o visto na Listagem 6.
Listagem 6. Código da classe AlunoBean.
package br.com.jm.mb;
//imports omitidos
@ManagedBean
@SessionScoped
public class AlunoMB implements Serializable {
private static final long serialVersionUID = 1L;
private Aluno aluno = new Aluno();
private List<Aluno> lista = new ArrayList<Aluno>();
public void buscar(){
AlunoDAO alunoDAO = new AlunoDAO();
aluno = alunoDAO.getAlunoByMatricula(aluno.getMatricula());
}
public String salvar() {
AlunoDAO alunoDAO = new AlunoDAO();
alunoDAO.adicionarAluno(aluno);
return "sucesso";
}
public String editar(){
AlunoDAO alunoDAO = new AlunoDAO();
alunoDAO.atualizarAluno(aluno);
return "sucesso_edi";
}
public String remover() {
AlunoDAO alunoDAO = new AlunoDAO();
alunoDAO.apagarAluno(aluno.getMatricula());
return "sucesso_del";
}
//métodos gets e sets omitidos.
}
Criando a camada view da aplicação
O próximo passo será criar as páginas web por onde as informações do cadastro serão manipuladas, assim como uma tela de autenticação do usuário, que será a primeira que criaremos.
Trata-se de um formulário simples que se comunica com o bean gerenciado UsuarioMB e que recebe os dados de acesso do usuário. Para criá-lo, clique com o botão direito do mouse sobre o projeto, selecione New > HTML File, insira o nome login.xhtml e clique em Next. Na lista que surgir, escolha o tipo de template HTML (xhtml 1.0 strict) e clique em Finish. A página deve ficar com o código semelhante ao apresentado na Listagem 7.
Listagem 7. Formulário de login. <h:body>
<h:form>
<h3>Informe os dados de acesso:</h3>
<h:outputText value="Username:" />
<h:inputText id="username" value="#{usuarioMB.usuario.user}"></h:inputText>
<h:message for="username"></h:message>
<br /><br />
<h:outputText value="Password:" />
<h:inputSecret id="password" value="#{usuarioMB.usuario.pwd}"></h:inputSecret>
<h:message for="password"></h:message>
<br /><br />
<h:commandButton action="#{usuarioMB.validateUsernamePassword}" value="Login" />
<h:commandButton value="Limpar" action="#{usuarioMB.limpar}" />
</h:form> </h:body>
A tela seguinte é o menu principal da aplicação e contém uma lista com todas as funcionalidades ofertadas pela aplicação. Esse arquivo é chamado de menu.xhtml. Um trecho do código pode ser visto através da Listagem 8.
Listagem 8. Menu Principal.
<h3>Bem vindo ao AcadêmicoNET</h3>
<ul id="nav">
<li><h4>Aluno</h4>
<ul>
<li><a href="cadastro_aluno.xhtml">Cadastrar</a></li>
<li><a href="remover_aluno.xhtml">Remover</a></li>
<li><a href="listar_aluno.xhtml">Listar</a></li>
<li><a href="editar_aluno.xhtml">Editar</a></li>
</ul>
</li> </ul>
O formulário de cadastro aparece no arquivo cadastro_aluno.xhtml e está representado através da Listagem 9.
Listagem 9. Formulário de cadastro de aluno.
<h:body>
<h2>Preencha o formulário abaixo</h2>
<h:form id="frmAluno" method="post">
<h:panelGrid columns="2" style="horizontal-align:center">
<h:outputText value="Nome:" />
<h:inputText value="#{alunoMB.aluno.nome}" />
<h:outputText value="Matrícula:" />
<h:inputText value="#{alunoMB.aluno.matricula}" />
<h:outputText value="CPF:" />
<h:inputText value="#{alunoMB.aluno.cpf}" />
<h:outputText value="Email:" />
<h:inputText value="#{alunoMB.aluno.email}" />
<h:outputText value="Cidade:" />
<h:inputText value="#{alunoMB.aluno.cidade}"/>
</h:panelGrid>
<h:commandButton action="#{alunoMB.salvar}" value="Enviar" />
<h:commandButton action="#{alunoMB.limpar}" value="Limpar" />
<input type='button' onclick='javascript:history.back()' value='Voltar' name='Voltar'/>
</h:form> </h:body>
O código das demais páginas da aplicação podem ser acessados através do projeto completo que estará disponível no site desta edição da revista Java Magazine.
Configurando a aplicação
Após a implementação das camadas view, model e controller, vamos partir para a configuração, informando ao Hibernate todas as informações do banco de dados ao qual ele se conectará e também registrando os eventos que permitirão ao Envers verificar se alguma entidade auditada sofreu alteração.
Essas configurações ficarão todas dispostas no arquivo hibernate.cfg.xml que deverá ser criado dentro da pasta src/main/resources. Dessa forma, escolha Novo > Documento XML, com o projeto selecionado, e informe o nome do arquivo. Seu conteúdo pode ser visto através da Listagem 10.
Listagem 10. Conteúdo do arquivo hibernate.cfg.xml.
<hibernate-configuration>
<session-factory>
<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:
mysql://localhost:3306/escolabd?zeroDateTimeBehavior=convertToNull</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">1234</property>
<property name="hibernate.show_sql">true</property>
<property name="hibernate.hbm2ddl.auto">create</property>
<mapping class="br.com.jm.entidade.Aluno"/>
<listener class="org.hibernate.envers.event.AuditEventListener" type="post-insert" />
<listener class="org.hibernate.envers.event.AuditEventListener" type="post-update" />
<listener class="org.hibernate.envers.event.AuditEventListener" type="post-delete" />
<listener class="org.hibernate.envers.event.AuditEventListener" type="pre-collection-update" />
<listener class="org.hibernate.envers.event.AuditEventListener" type="pre-collection-remove" />
<listener class="org.hibernate.envers.event.AuditEventListener" type="post-collection-recreate" />
</session-factory> </hibernate-configuration>
A partir da linha 14é possível visualizar os event listener que são classes de escuta responsáveis por controlar as tabelas de histórico, incluíndo registros nelas de acordo com a ação realizada na aplicação. Esses listeners devem estar presentes no arquivo de configuração, senão de nada vai adiantar a inclusão das anotações nas entidades auditáveis.
Criando a conexão com o banco de dados
Após criado o arquivo de configuração, vamos criar a classe utilitária HibernateUtil que fará a ligação entre o hibernate.cfg.xml e o banco de dados, disponibilizando uma instância de SessionFactory para a aplicação. Essa classe ficará dentro do pacote br.com.jm.util e seu código pode ser visto na Listagem 11.
Listagem 11. Código da classe HibernateUtil.
package br.com.jm.util;
//imports omitidos
public class HibernateUtil {
private static SessionFactory sessionFactory;
public static SessionFactory getSessionFactory() {
if (sessionFactory == null) {
Configuration configuration = new Configuration().configure();
ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder()
.applySettings(configuration.getProperties()).build();
sessionFactory = configuration.buildSessionFactory(serviceRegistry);
SchemaUpdate se = new SchemaUpdate(configuration);
se.execute(true, true);
}
return sessionFactory;
}
}
Persistindo a entidade Aluno
Agora que as classes mapeadas com as anotações de auditoria, o arquivo de configuração e a conexão com o banco foram implementadas, criaremos a classe que será responsável por viabilizar as operações de persistência. O pacote em questão é o br.com.jm.acessobd.
Após criado o pacote, clique sobre ele com o botão direito, escolha New > Class e dê o nome à classe de AlunoDAO. A Listagem 12 mostra seu código-fonte.
Listagem 12. Código da classe AlunoDAO.
package br.com.jm.acessobd;
//imports omitidos
public class AlunoDAO {
private static SessionFactory factory;
public AlunoDAO() {
factory = HibernateUtil.getSessionFactory();
}
public Integer adicionarAluno(Aluno aluno) {
Session session = factory.openSession();
Transaction tx = null;
Integer cod_aluno = null;
try {
tx = session.beginTransaction();
cod_aluno = (Integer) session.save(aluno);
tx.commit();
}
catch (HibernateException e) {
if (tx != null) {
tx.rollback();
}
e.printStackTrace();
} finally {
session.close();
}
return cod_aluno;
}
public List listarAlunos() {
Session session = factory.openSession();
Transaction tx = null;
List alunos = null;
try {
tx = session.beginTransaction();
alunos = session.createCriteria(Aluno.class).list();
tx.commit();
} catch (HibernateException e) {
if (tx != null) {
tx.rollback();
}
e.printStackTrace();
} finally {
session.close();
} return alunos;
}
public void atualizarAluno(Aluno aluno) {
Session session = factory.openSession();
Transaction tx = null;
Aluno aluno_upd = new Aluno();
try {
tx = session.beginTransaction();
aluno_upd.setCidade(aluno.getCidade());
aluno_upd.setCodigo(aluno.getCodigo());
aluno_upd.setCpf(aluno.getCpf());
aluno_upd.setEmail(aluno.getEmail());
aluno_upd.setMatricula(aluno.getMatricula());
aluno_upd.setNome(aluno.getNome());
session.update(aluno_upd);
tx.commit();
} catch (HibernateException e) {
if (tx != null) {
tx.rollback();
}
e.printStackTrace();
} finally {
session.close();
}
}
public void apagarAluno(Integer matricula) {
Session session = factory.openSession();
Transaction tx = null;
try {
tx = session.beginTransaction();
Criteria criteria = session.createCriteria(Aluno.class);
criteria.add(Restrictions.eq("matricula", matricula));
List results = criteria.list();
session.delete((Aluno)results.get(0));
tx.commit();
} catch (HibernateException e) {
if (tx != null) {
tx.rollback();
}
e.printStackTrace();
} finally {
session.close();
}
}
public Aluno getAlunoByMatricula(int matricula) {
Session session = factory.openSession();
Transaction tx = null;
Aluno aluno = null;
try {
tx = session.beginTransaction();
Criteria criteria = session.createCriteria(Aluno.class);
criteria.add(Restrictions.eq("matricula", matricula));
List results = criteria.list();
aluno = ((Aluno)results.get(0));
tx.commit();
} catch (HibernateException e) {
if (tx != null) {
tx.rollback();
}
e.printStackTrace();
} finally {
session.close();
}
return aluno;
}
}
Auditando informações extras Nesse ponto temos toda aplicação configurada e funcional, portanto, pronta para ser executada. Porém, ainda não estamos atendendo a um dos requisitos propostos no começo desse artigo que é a possibilidade de customizar informações, adicionando atributos adicionais junto à estrutura de auditoria do Hibernate Envers. Conforme mencionamos antes, além das informações default, duas outras devem ser registradas: usuário e o endereço IP.
Para isso, vamos utilizar-se do recurso de escuta de revisão do framework e criar uma classe que representará a entidade de revisão do projeto, sendo responsável por mapear todos os dados que queremos armazenar no momento da criação de uma nova revisão.
Sendo assim, dentro do pacote br.com.jm.entidade, crie a classe AuditEntity. A Listagem 13 mostra como foi realizada sua implementação.
Na linha 5 a classe foi mapeada como uma entidade JPA, através da anotation @Entity. Além disso, é feita a alteração do nome da tabela central de auditoria do Envers, que, por padrão, leva o nome de "revinfo". Através do atributo "name" foi definido o novo nome como sendo "revinfo_cust".
A anotação @RevisionEntity, presente na linha 6, indica que essa é uma entidade de revisão e que será usada para armazenar o histórico das revisões. Nessa mesma linha é feito menção a classe AuditListener, que vai ser um “interceptor” da classe RevisionListener e implementará o comportamento customizado que será utilizado no momento da criação da revisão.
Os novos campos que serão inseridos na tabela de revisão aparecem definidos nas linhas 11 e 12. Os demais campos, que são default do framework, são incorporados através da classe DefaultRevisionEntity, estendida na linha 7.
Outra classe que deve ser criada é a AuditListener que estende AuditEventListener, como mostra a Listagem 14.
Listagem 13. Código da classe AuditEntity.
package br.com.jm.entidade; //imports omitidos @Entity(name="revinfo_cust") @RevisionEntity(AuditListener.class) public class AuditEntity extends DefaultRevisionEntity { private static final long serialVersionUID = 1L; public String usuario; public String ip; //gets e sets omitidos }
Nesta classe, o método newRevision – linha 7 – foi sobrescrito. O mesmo recebe como parâmetro a entidade que está sendo auditida, que no nosso caso é a Aluno. Esse método é chamado toda vez que o Envers vai criar uma nova revisão, assim, podemos instanciar nossa classe AuditEntity e definir os atributos que queremos nela. Nas linhas 9 e 10 adicionei, o usuário que está realizando a alteração, e o IP de sua máquina. Os atributos que vêm da DefaultRevisionEntity (id e timestamp), são preenchidos automaticamente pelo Envers.
Listagem 14. Código da classe AuditListener.
package br.com.jm.entidade;
//imports omitidos
@Entity(name="revinfo_cust")
@RevisionEntity(AuditListener.class)
public class AuditEntity extends DefaultRevisionEntity {
private static final long serialVersionUID = 1L;
public String usuario;
public String ip;
//gets e sets omitidos
}
Além da criação dessas duas classes, uma outra configuração deve ser incorporada ao arquivo hibernate.cfg.xml que representa a identificação da nova entidade criada. Portanto, adicione o seguinte trecho no arquivo ao arquivo mencionado.
<mapping class="br.com.jm.entidade.AuditEntity"/>
Testando o funcionamento do Hibernate Envers
Enfim, chegamos ao ponto em que podemos testar a aplicação e ver o Envers trabalhando. Para execução da aplicação, clique com o botão direito do mouse sobre o projeto, acesse Run As > Run on Server, escolha o Tomcat como servidor e clique em Finish. Feito isso, a primeira tela que se aparecerá é a tela de login, conforme mostra a Figura 3. Portanto, informe os dados de acesso. Caso estejam corretos, uma sessão será criada para o usuário e o mesmo será direcionado para o menu da aplicação, mostrado pela Figura 4.
Figura 3. Tela de Login
Figura 4. Menu da Aplicação
A primeira ação que executaremos frente ao sistema é o cadastro de um aluno. Após isso, já conseguiremos ver os efeitos do Hibernate Envers no banco de dados. Clique em Cadastrar na tela em questão e digite as informações do aluno na próxima que aparecer. Após clique em enviar, conforme Figura 5.
Figura 5. Tela de cadastro de aluno.
A Figura 6 mostra o resultado da primeira inserção efetuada no banco.
Figura 6. Visão do Banco de Dados.
Após realizar o cadastro, além de ser inserido o registro na tabela aluno, duas novas tabelas foram criadas, sendo elas: aluno_auditoria que contém o histórico (modificação, criação e remoção) referente a entidade Aluno e a tabela revinfo_cust que indica em que momento foi criada a revisão e também armazena as informações personalizadas. Após o primeiro cadastro, veja a visão das duas tabelas, através da Figura 7.
Figura 7. Consulta Banco de Dados.
Na tabela aluno_auditoria, além de todos os atributos auditáveis da tabela aluno, duas novas colunas aparecem: REV e REVTYPE. A primeira refere-se à identificação da revisão e a segunda diz respeito ao tipo de operação realizada que pode ser 0, 1 ou 2 indicando, respectivamente, inserção, edição ou remoção. Veja que nesse caso, o número 0 indica que foi realizado a inclusão de um registro na tabela auditada.
Já a tabela revinfo_cust possui, por padrão, dois campos: id e timestamp. O primeiro simboliza a identificação da revisão, que também aparece na tabela aluno_auditoria, enquanto que o segundo indica o momento em que foi realizado a alteração. As demais colunas que aparecem nessa tabela foram customizadas.
Agora vamos realizar mais algumas ações sobre o banco e ver o comportamento das nossas tabelas de auditoria. Será cadastrado mais três alunos. Veja o resultado na Figura 8.
Figura 8. Consulta Banco de Dados.
Perceba que os valores da coluna REV na tabela aluno_auditoria mudaram, pois são revisões diferentes. Lembrando que uma revisão no Envers representa uma transação. Já o valor da coluna REVTYPE se manteve o mesmo, pois em todos os 4 registros, a operação realizada sobre eles foi a de inserção.
Aqui vamos editar um dos alunos e ver o resultado.
Figura 9. Conteúdo da tabela aluno_auditoria.
A Figura 9 mostra o conteúdo da tabela aluno_auditoria. Foi editado o aluno de código 39301 sendo alterado a sua cidade de Uberlândia para Uberaba. Em virtude dessa ação, foi adicionado um novo registro nessa tabela. Note que agora na coluna REVTYPE aparece o valor 1 pois trata-se de uma alteração de registro. Vamos agora deletar um aluno e ver o resultado. Analisando a consulta da Figura 10 veja que o aluno de código 2 foi deletado. O valor 2 para o campo REVTYPE indica que houve uma exclusão.
Figura 10. Consulta Banco de Dados.
Consulta e recuperação dos dados de auditoria
O Envers oferece um mecanismo para que seja possível recuperar o histórico de mudanças de uma entidade através de queries que são semelhantes ao Hibernate Criteria. Isso pode ser feito usando a interface AuditReader, que contém operações de busca, baseada nas entidades mapeadas.
Por exemplo, na Listagem 15 temos um código que mostra como recuperar o número máximo de revisões produzidas pelas alterações que fizemos no banco. Para se ter acesso as revisões produzidas, primeiro precisamos criar uma instância de AuditReader, o que pode ser feito através do método getAuditReader, visto na linha 3.
Listagem 15. Recuperação das revisões.
public int getRevisions(){
Number revision = (Number) getAuditReader().createQuery()
.forRevisionsOfEntity(Aluno.class, false, true)
.setProjection(AuditEntity.revisionNumber().min())
.add(AuditEntity.id().eq(entityId))
.add(AuditEntity.revisionNumber().gt(42))
.getSingleResult();
return revision;
}
Conclusão
Uma das principais características oferecidas pelo Hibernate Envers, além de sua simplicidade, é o ganho em produtividade e a redução de tempo empregada na manutenção. Enquanto, com outros recursos, perde-se muito tempo e utiliza-se muitas linhas de código, com esse framework temos apenas que inserir algumas configurações e poucas anotações no código.
Além disso, não precisamos ficar preso aos recursos default que a biblioteca oferece podendo adicionar metadados a uma revisão e customizar nossas informações, identificando por exemplo, o usuário que fez determinada alteração, ou indo além, determinando o IP da máquina do usuário, seu Sistema Operacional ou mesmo outras informações.
Links Site oficial do Hibernate Envers. //hibernate.org/orm/envers/
Site oficial do MySQL. //www.mysql.com/
Endereço para download do driver do MySQL. //dev.mysql.com/downloads/connector/j/
Site oficial do Eclipse. //eclipse.org/downloads/
Endereço para download do JDK. //www.oracle.com/technetwork/java/javase/downloads
Endereço para download do Maven. //maven.apache.org/download.html
Carlos Alberto Silva é Formado em Ciência da Computação pela Universidade Federal de Uberlândia (UFU), com especialização em Desenvolvimento Java pelo Centro Universitário do Triângulo (UNITRI). Trabalha na empresa Algar Telecom como Analista de Sistemas e atualmente é aluno do curso de especialização em Análise e Desenvolvimento de Sistemas Aplicados a Gestão Empresarial no Instituto Federal do Triângulo Mineiro (IFTM). Possui as seguintes certificações: OCJP, OCWCD e ITIL.
Este artigo foi revisto pela equipe de produtos Oracle e está em conformidade com as normas e práticas para o uso de produtos Oracle.